// Copyright 2016 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "draco/attributes/point_attribute.h"

#include <unordered_map>

using std::unordered_map;

// Shortcut for typed conditionals.
template <bool B, class T, class F>
using conditional_t = typename std::conditional<B, T, F>::type;

namespace draco {

    PointAttribute::PointAttribute()
        : num_unique_entries_( 0 ), identity_mapping_( false ) {}

    PointAttribute::PointAttribute ( const GeometryAttribute &att )
        : GeometryAttribute( att ),
        num_unique_entries_( 0 ),
        identity_mapping_( false ) {}

    void PointAttribute::Init ( Type attribute_type, int8_t num_components,
            DataType data_type, bool normalized,
            size_t num_attribute_values ) {

        attribute_buffer_ = std::unique_ptr<DataBuffer>( new DataBuffer() );
        GeometryAttribute::Init(attribute_type, attribute_buffer_.get(),
                num_components, data_type, normalized,
                DataTypeLength( data_type ) * num_components, 0 );

        Reset( num_attribute_values );
        SetIdentityMapping();
    }

    void PointAttribute::CopyFrom ( const PointAttribute &src_att ) {

        if ( buffer() == nullptr ) {

            // If the destination attribute doesn't have a valid buffer, create it.
            attribute_buffer_ = std::unique_ptr<DataBuffer>( new DataBuffer() );
            ResetBuffer( attribute_buffer_.get(), 0, 0 );
        }

        if ( !GeometryAttribute::CopyFrom( src_att ) ) {
            return;
        }

        identity_mapping_ = src_att.identity_mapping_;
        num_unique_entries_ = src_att.num_unique_entries_;
        indices_map_ = src_att.indices_map_;

        if ( src_att.attribute_transform_data_ ) {

            attribute_transform_data_ = std::unique_ptr<AttributeTransformData>(
                    new AttributeTransformData( *src_att.attribute_transform_data_ ) );
        } else {

            attribute_transform_data_ = nullptr;
        }
    }

    bool PointAttribute::Reset ( size_t num_attribute_values ) {

        if ( attribute_buffer_ == nullptr ) {
            attribute_buffer_ = std::unique_ptr<DataBuffer>( new DataBuffer() );
        }

        const int64_t entry_size = DataTypeLength( data_type() ) * num_components();

        if ( !attribute_buffer_->Update( nullptr, num_attribute_values * entry_size ) ) {

            return false;
        }

        // Assign the new buffer to the parent attribute.
        ResetBuffer( attribute_buffer_.get(), entry_size, 0 );
        num_unique_entries_ = static_cast<uint32_t>( num_attribute_values );

        return true;
    }

    void PointAttribute::Resize ( size_t new_num_unique_entries ) {
        num_unique_entries_ = static_cast<uint32_t>( new_num_unique_entries );
        attribute_buffer_->Resize( new_num_unique_entries * byte_stride() );
    }

#ifdef DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED

    AttributeValueIndex::ValueType PointAttribute::DeduplicateValues (
            const GeometryAttribute &in_att ) {

        return DeduplicateValues( in_att, AttributeValueIndex( 0 ) );
    }

    AttributeValueIndex::ValueType PointAttribute::DeduplicateValues (
            const GeometryAttribute &in_att, AttributeValueIndex in_att_offset ) {

        AttributeValueIndex::ValueType unique_vals = 0;

        switch ( in_att.data_type() ) {
            // Currently we support only float, uint8, and uint16 arguments.
            case DT_FLOAT32:
                unique_vals = DeduplicateTypedValues<float>( in_att, in_att_offset );
                break;
            case DT_INT8:
                unique_vals = DeduplicateTypedValues<int8_t>( in_att, in_att_offset );
                break;
            case DT_UINT8:
            case DT_BOOL:
                unique_vals = DeduplicateTypedValues<uint8_t>( in_att, in_att_offset );
                break;
            case DT_UINT16:
                unique_vals = DeduplicateTypedValues<uint16_t>( in_att, in_att_offset );
                break;
            case DT_INT16:
                unique_vals = DeduplicateTypedValues<int16_t>( in_att, in_att_offset );
                break;
            case DT_UINT32:
                unique_vals = DeduplicateTypedValues<uint32_t>( in_att, in_att_offset );
                break;
            case DT_INT32:
                unique_vals = DeduplicateTypedValues<int32_t>( in_att, in_att_offset );
                break;
            default:
                return -1;  // Unsupported data type.
        }

        if ( unique_vals == 0 ) {
            return -1;  // Unexpected error.
        }

        return unique_vals;
    }

    // Helper function for calling UnifyDuplicateAttributes<T,num_components_t>
    // with the correct template arguments.
    // Returns the number of unique attribute values.
    template <typename T>
        AttributeValueIndex::ValueType PointAttribute::DeduplicateTypedValues (
                const GeometryAttribute &in_att, AttributeValueIndex in_att_offset ) {

            // Select the correct method to call based on the number of attribute
            // components.
            switch ( in_att.num_components() ) {
                case 1:
                    return DeduplicateFormattedValues<T, 1>( in_att, in_att_offset );
                case 2:
                    return DeduplicateFormattedValues<T, 2>( in_att, in_att_offset );
                case 3:
                    return DeduplicateFormattedValues<T, 3>( in_att, in_att_offset );
                case 4:
                    return DeduplicateFormattedValues<T, 4>( in_att, in_att_offset );
                default:
                    return 0;
            }
        }

    template <typename T, int num_components_t>
        AttributeValueIndex::ValueType PointAttribute::DeduplicateFormattedValues (
                const GeometryAttribute &in_att, AttributeValueIndex in_att_offset ) {

            // We want to detect duplicates using a hash map but we cannot hash floating
            // point numbers directly so bit-copy floats to the same sized integers and
            // hash them.

            // First we need to determine which int type to use (1, 2, 4 or 8 bytes).
            // Note, this is done at compile time using std::conditional struct.
            // Conditional is in form <bool-expression, true, false>. If bool-expression
            // is true the "true" branch is used and vice versa. All at compile time.
            typedef conditional_t<sizeof( T ) == 1, uint8_t,
                    conditional_t<sizeof( T ) == 2, uint16_t,
                    conditional_t<sizeof( T ) == 4, uint32_t,
                    /*else*/ uint64_t>>>
                        HashType;

            AttributeValueIndex unique_vals( 0 );

            typedef std::array<T, num_components_t> AttributeValue;
            typedef std::array<HashType, num_components_t> AttributeHashableValue;

            // Hash map storing index of the first attribute with a given value.
            unordered_map<AttributeHashableValue, AttributeValueIndex,
                HashArray<AttributeHashableValue>>
                    value_to_index_map;

            AttributeValue att_value;
            AttributeHashableValue hashable_value;
            IndexTypeVector<AttributeValueIndex, AttributeValueIndex> value_map( num_unique_entries_ );

            for ( AttributeValueIndex i( 0 ); i < num_unique_entries_; ++i ) {

                const AttributeValueIndex att_pos = i + in_att_offset;
                att_value = in_att.GetValue<T, num_components_t>( att_pos );

                // Convert the value to hashable type. Bit-copy real attributes to integers.
                memcpy( &( hashable_value[ 0 ] ), &( att_value[ 0 ] ), sizeof( att_value ) );

                // Check if the given attribute value has been used before already.
                auto it = value_to_index_map.find( hashable_value );

                if ( it != value_to_index_map.end() ) {
                    // Duplicated value found. Update index mapping.
                    value_map[ i ] = it->second;
                } else {
                    // New unique value.
                    // Update the hash map with a new entry pointing to the latest unique
                    // vertex index.
                    value_to_index_map.insert(
                            std::pair<AttributeHashableValue, AttributeValueIndex>( hashable_value,
                                unique_vals ) );

                    // Add the unique value to the mesh builder.
                    SetAttributeValue( unique_vals, &att_value );

                    // Update index mapping.
                    value_map[ i ] = unique_vals;

                    ++unique_vals;
                }
            }

            if ( unique_vals == num_unique_entries_ ) {

                return unique_vals.value();  // Nothing has changed.
            }

            if ( is_mapping_identity() ) {

                // Change identity mapping to the explicit one.
                // The number of points is equal to the number of old unique values.
                SetExplicitMapping(num_unique_entries_);

                // Update the explicit map.
                for ( uint32_t i = 0; i < num_unique_entries_; ++i ) {
                    SetPointMapEntry( PointIndex( i ), value_map[ AttributeValueIndex( i ) ] );
                }
            } else {
                // Update point to value map using the mapping between old and new values.
                for ( PointIndex i( 0 ); i < static_cast<uint32_t>( indices_map_.size() ); ++i ) {

                    SetPointMapEntry( i, value_map[ indices_map_[ i ] ] );
                }
            }
            num_unique_entries_ = unique_vals.value();

            return num_unique_entries_;
        }
#endif

}  // namespace draco
